import sourceTypeEnum from "../enum/sourceType.enum"
import sensorTypeEnum from "../enum/sensorType.enum"
import SdkCommand from "../command/sdk.command"
import { ClassFactory, copyPosition, DependencyFetcher, exportByKey, isNum, isPositon, isSamePoint } from "./utils"
import { DependencyObserverAdaptor, SdkCollection, SdkObservable } from "./observable"
function isString(e) {
  return !!e && "string" == typeof e
}
function isObject(e) {
  return !!e && "object" == typeof e
}
function isOrientation(e) {
  if (e && "object" == typeof e && "yaw" in e && "pitch" in e && "roll" in e) {
    const t = e
    return isNum(t.yaw) && isNum(t.pitch) && isNum(t.roll)
  }
  return !1
}
class SensorAddSourceExecutor {
  validateInput(e, t, o) {
    if (!isString(e.sensorId) || !t.has(e.sensorId)) throw Error("dev error: this should not occur unless the sdk was not setup properly")
    if (
      !(function (e, t) {
        return !!Array.isArray(e) && e.every(t)
      })(e.sourceIds, isString)
    )
      throw Error("dev error: this should not occur unless the sdk was not setup properly")
    for (const t of e.sourceIds) if (!o.has(t)) throw Error("dev error: this should not occur unless the sdk was not setup properly")
    return { sensorId: e.sensorId, sourceIds: e.sourceIds }
  }
  exec(e, t, o, n) {
    const r = o.get(e.sensorId),
      a = e.sourceIds.map(n.get, n)
    r.addSource(...a), t.return()
  }
}
class DisposeSensorExecutor {
  validateInput(e, t) {
    if (!isString(e.sensorId) || !t.has(e.sensorId)) throw Error("dev error: this should not occur unless the sdk was not setup properly")
    return { sensorId: e.sensorId }
  }
  exec(e, t, o, n) {
    n.get(e.sensorId).dispose()
    o.get(e.sensorId).dispose(), t.return()
  }
}
class SensorShowDebugExecutor {
  validateInput(e, t, o) {
    if (!isString(e.sensorId) || !o.has(e.sensorId)) throw Error("dev error: this should not occur unless the sdk was not setup properly")
    if (
      !(function (e) {
        return "boolean" == typeof e
      })(e.show)
    )
      throw Error(`Sensor.showDebug: 'show' was not a boolean; got ${e.show}`)
    return { sensorId: e.sensorId, show: e.show }
  }
  exec(e, t, o, n) {
    if (e.show) {
      const t = n.get(e.sensorId)
      o.setDebugSensor(t)
    } else o.setDebugSensor(null)
    t.return()
  }
}
class SensorStateObservable {
  _data: { origin: { x: number; y: number; z: number }; forward: { x: number; y: number; z: number } }
  constructor() {
    this._data = { origin: { x: 0, y: 0, z: 0 }, forward: { x: 0, y: 0, z: -1 } }
  }
  get data() {
    return this._data
  }
  equals(e) {
    return isSamePoint(this.data.origin, e.data.origin) && isSamePoint(this.data.forward, e.data.forward)
  }
  copy(e) {
    copyPosition(this._data.origin, e.data.origin), copyPosition(this._data.forward, e.data.forward)
  }
  update(e) {
    copyPosition(this._data.origin, e.frustum.origin), copyPosition(this._data.forward, e.frustum.forward)
  }
}
class CreateSensorExecutor {
  sdkModule: any
  sensorFactories: any
  static nextSensorId: any
  constructor(e, t) {
    this.sdkModule = e
    this.sensorFactories = t
  }
  validateInput(e) {
    if (!Object.values(sensorTypeEnum).includes(e.type)) throw Error(e.type + " is not a valid sensor type")
    return this.sensorFactories[e.type].validateInput(e)
  }
  async exec(e, t, o, n, r) {
    const a = this.sensorFactories[e.type].create(e, o),
      s = "sensor-" + ++CreateSensorExecutor.nextSensorId
    n.set(s, a)
    const i = SdkObservable.create(new DependencyFetcher(a), new SensorStateSubscriptionFactory(), new ClassFactory(SensorStateObservable)),
      d = this.sdkModule.addObservable(s, i),
      c = SdkCollection.create(new DependencyFetcher(a), new SensorReadingSubscriptionFactory(), new ClassFactory(SensorReadingCollection)),
      u = this.sdkModule.addCollection(s, c)
    r.set(s, {
      dispose() {
        d.dispose(), u.dispose()
      }
    }),
      t.return({ sensorId: s })
  }
}
CreateSensorExecutor.nextSensorId = 0
class CameraSensorFactory {
  validateInput(e) {
    return e
  }
  create(e, t) {
    return t.createCameraBoundSensor()
  }
}
class SensorStateSubscriptionFactory {
  create(e, t) {
    return t.frustum.observe(new DependencyObserverAdaptor(e))
  }
}
class SensorReadingSubscriptionFactory {
  create(e, t) {
    return t.onReadingsUpdated(new DependencyObserverAdaptor(e))
  }
}
class SphereSourceUpdater {
  validateInput(e) {
    if (!isObject(e.options)) throw Error("Source.Sphere.update: invalid options provided to .update")
    if (!isPositon(e.options.origin)) throw Error(`Source.Sphere.update: 'origin' was specified but was not a valid Vector3; got ${e.options.origin}`)
    if (!isNum(e.options.radius) || e.options.radius < 0)
      throw Error(`Source.Sphere.update: 'radius' was specified but was not a positive number or Infinity; got ${e.options.radius}`)
    return e
  }
  update(e, t) {
    const o = t.volume
    o.updateOrigin(e.options.origin), o.updateRadius(e.options.radius), o.notify()
  }
}
class BoxSourceUpdater {
  validateInput(e) {
    if (!isObject(e.options)) throw Error("Source.Box.update: invalid options provided to .update")
    if (e.options.center && !isPositon(e.options.center))
      throw Error(`Source.Box.update: 'center' was specified but was not a valid Vector3; got ${e.options.center}`)
    if (e.options.size && !(isPositon(e.options.size) && e.options.size.x < 0 && e.options.size.y < 0 && e.options.size.z < 0))
      throw Error(`Source.Box.update: 'size' was specified but was not a valid Vector3; got ${e.options.size}`)
    if (e.options.orientation && !isOrientation(e.options.orientation))
      throw Error(`Source.Box.update: 'orientation' was specified but was not a valid Orientation; got ${e.options.orientation}`)
    return e
  }
  update(e, t) {
    const o = t.volume
    o.updateCenter(e.options.center), o.updateDimensions(e.options.size), t.updateOrientation(e.options.orientation), o.notify()
  }
}
class CylinderSourceUpdater {
  validateInput(e) {
    if (!isObject(e.options)) throw Error("Source.Cylinder.update: invalid options provided to .update")
    if (e.options.basePoint && !isPositon(e.options.basePoint))
      throw Error(`Source.Cylinder.update: 'basePoint' was specified but was not a valid Vector3; got ${e.options.basePoint}`)
    if (e.options.hasOwnProperty("height") && !isNum(e.options.height))
      throw Error(`Source.Cylinder.update: 'height' was specified but was not a positive number or Infinity; got ${e.options.height}`)
    if (e.options.hasOwnProperty("radius") && !isNum(e.options.radius))
      throw Error(`Source.Cylinder: 'radius' was specified but was not a positive number or Infinity; got ${e.options.radius}`)
    return e
  }
  update(e, t) {
    const o = t.volume
    o.updateBasePoint(e.options.basePoint), o.updateHeight(e.options.height), o.updateRadius(e.options.radius), o.notify()
  }
}
class UpdateSourceExecutor {
  sourceUpdaters: any
  constructor(e) {
    this.sourceUpdaters = e
  }
  validateInput(e, t) {
    if (!isString(e.sourceId) || !t.has(e.sourceId)) throw Error("dev error: this should not occur unless the sdk was not setup properly")
    const o = t.get(e.sourceId)
    return this.sourceUpdaters[o.type].validateInput(e)
  }
  exec(e, t, o) {
    const n = o.get(e.sourceId)
    this.sourceUpdaters[n.type].update(e, n), t.return()
  }
}
class SphereSourceFactory {
  sourceType: any
  sphereVolumeFactory: any
  THREE: any
  constructor(e, t, o) {
    this.sourceType = e
    this.sphereVolumeFactory = t
    this.THREE = o
  }
  validateInput(e) {
    if (isObject(e.options)) {
      if (e.options.origin && !isPositon(e.options.origin))
        throw Error(`Source.Box: 'origin' was specified but was not a valid Vector3; got ${e.options.origin}`)
      if (e.options.radius && (!isNum(e.options.radius) || e.options.radius < 0))
        throw Error(`Source.Sphere: 'height' was specified but was not a positive number or Infinity; got ${e.options.height}`)
    }
    const t = e.options || {}
    return {
      type: e.type,
      options: {
        origin: t.origin || { x: 0, y: 0, z: 0 },
        radius: t.hasOwnProperty("radius") ? t.radius : 1 / 0
      }
    }
  }
  create(e) {
    const t = new SphereSource(this.sourceType, new this.sphereVolumeFactory(), this.THREE),
      o = t.volume
    return o.updateOrigin(e.options.origin), o.updateRadius(e.options.radius), t
  }
}
class SphereSource {
  radius: number
  type: any
  _volume: any
  constructor(e, t, o) {
    this.radius = 1 / 0
    this.type = e.SPHERE
    this._volume = t
  }
  get origin() {
    return this._volume.origin
  }
  get volume() {
    return this._volume
  }
  describeVolume() {
    return { origin: this._volume.origin, radius: this._volume.radius }
  }
}
class BoxSourceFactory {
  sourceType: any
  boxVolumeFactory: any
  THREE: any
  constructor(e, t, o) {
    this.sourceType = e
    this.boxVolumeFactory = t
    this.THREE = o
  }
  validateInput(e) {
    if (isObject(e.options)) {
      if (e.options.center && !isPositon(e.options.center))
        throw Error(`Source.Box: 'center' was specified but was not a valid Vector3; got ${e.options.center}`)
      if (e.options.size && (!isPositon(e.options.size) || e.options.size.x < 0 || e.options.size.y < 0 || e.options.size.z < 0))
        throw Error(`Source.Box: 'size' was specified but was not a valid Vector3; got ${e.options.size}`)
      if (e.options.orientation && !isOrientation(e.options.orientation))
        throw Error(`Source.Box: 'orientation' was specified but was not a valid Orientation; got ${e.options.orientation}`)
    }
    const t = e.options || {}
    return {
      type: e.type,
      options: {
        center: t.center || { x: 0, y: 0, z: 0 },
        size: t.size || { x: 1 / 0, y: 1 / 0, z: 1 / 0 },
        orientation: t.orientation || { yaw: 0, pitch: 0, roll: 0 }
      }
    }
  }
  create(e) {
    const t = new BoxSource(this.sourceType, new this.boxVolumeFactory(), this.THREE),
      o = t.volume
    return o.updateCenter(e.options.center), o.updateDimensions(e.options.size), t.updateOrientation(e.options.orientation), t
  }
}
class BoxSource {
  type: any
  _volume: any
  eulerCache: any
  quaternionCache: any
  constructor(e, t, o) {
    this.type = e.BOX
    this._volume = t
    this.eulerCache = new o.Euler()
    this.quaternionCache = new o.Quaternion()
  }
  get origin() {
    return this._volume.origin
  }
  get volume() {
    return this._volume
  }
  describeVolume() {
    return {
      center: this._volume.center,
      size: this._volume.size,
      orientation: this.convertQuaternionToOrientation(this._volume.orientation)
    }
  }
  updateOrientation(e) {
    this.eulerCache.set((e.pitch * Math.PI) / 180, (e.yaw * Math.PI) / 180, (e.roll * Math.PI) / 180, "YXZ"),
      this.quaternionCache.setFromEuler(this.eulerCache),
      this._volume.updateOrientation(this.quaternionCache)
  }
  convertQuaternionToOrientation(e) {
    return (
      this.eulerCache.setFromQuaternion(this.quaternionCache.set(e.x, e.y, e.z, e.w)).reorder("YXZ"),
      { yaw: (this.eulerCache.y * Math.PI) / 180, pitch: (this.eulerCache.x * Math.PI) / 180, roll: (this.eulerCache.z * Math.PI) / 180 }
    )
  }
}
class CylinderSourceFactory {
  sourceType: any
  cylinderVolumeFactory: any
  THREE: any
  constructor(e, t, o) {
    this.sourceType = e
    this.cylinderVolumeFactory = t
    this.THREE = o
  }
  validateInput(e) {
    if (isObject(e.options)) {
      if (e.options.basePoint && !isPositon(e.options.basePoint))
        throw Error(`Source.Cylinder: 'basePoint' was specified but was not a valid Vector3; got ${e.options.basePoint}`)
      if (e.options.hasOwnProperty("height") && !(isNum(e.options.height) && e.options.height > 0))
        throw Error(`Source.Cylinder: 'height' was specified but was not a positive number or Infinity; got ${e.options.height}`)
      if (e.options.hasOwnProperty("radius") && !(isNum(e.options.radius) && e.options.radius > 0))
        throw Error(`Source.Cylinder: 'radius' was specified but was not a positive number or Infinity; got ${e.options.radius}`)
    }
    const t = e.options || {}
    return {
      type: e.type,
      options: {
        basePoint: t.basePoint || { x: 0, y: 0, z: 0 },
        height: t.hasOwnProperty("height") ? t.height : 1 / 0,
        radius: t.hasOwnProperty("radius") ? t.radius : 1 / 0
      }
    }
  }
  create(e) {
    const t = new CylinderSource(this.sourceType, new this.cylinderVolumeFactory(), this.THREE),
      o = t.volume
    return o.updateBasePoint(e.options.basePoint), o.updateHeight(e.options.height), o.updateRadius(e.options.radius), t
  }
}
class CylinderSource {
  type: any
  _volume: any
  constructor(e, t, o) {
    this.type = e.CYLINDER
    this._volume = t
  }
  get origin() {
    return this._volume.origin
  }
  get volume() {
    return this._volume
  }
  describeVolume() {
    return { basePoint: this._volume.basePoint, height: this._volume.height, radius: this._volume.radius }
  }
}
class CreateSourceExecutor {
  sourceFactories: any
  static nextSourceId: any
  constructor(e) {
    this.sourceFactories = e
  }
  validateInput(e, t) {
    if (!Object.values(sourceTypeEnum).includes(e.type)) throw Error(e.type + " is not a valid source type")
    return this.sourceFactories[e.type].validateInput(e)
  }
  exec(e, t, o) {
    const n = this.sourceFactories[e.type].create(e),
      r = "source-" + ++CreateSourceExecutor.nextSourceId
    o.set(r, n), t.return({ sourceId: r, type: e.type, volume: n.describeVolume() })
  }
}
class SensorReadingCollection {
  sourceMap: Map<any, any>
  reverseSourceLookup: Map<any, any>
  sourceId: number
  _data: {}
  constructor() {
    this.sourceMap = new Map()
    this.reverseSourceLookup = new Map()
    this.sourceId = 0
    this._data = {}
  }
  get data() {
    return this._data
  }
  isItemEqual(e, t) {
    return e.data[t].inRange === this.data[t].inRange && e.data[t].inView === this.data[t].inView
  }
  update(e) {
    for (const [t, o] of e.readings) {
      const e = this.addOrGetSourceId(t),
        n = this._data[e] || {}
      n.inRange = o.inRange
      n.inView = o.inView
      n.distanceSquared = o.distanceSq
      n.direction = exportByKey(o, "direction", n.direction)
      this._data[e] = n
    }
    for (const t in this.data) {
      const o = this.reverseSourceLookup.get(t)
      o && !e.readings.get(o) && (this.sourceMap.delete(o), this.reverseSourceLookup.delete(t), delete this._data[t])
    }
  }
  clear() {
    this._data = {}
  }
  addOrGetSourceId(e) {
    const t = this.sourceMap.get(e)
    if (t) return t
    const o = "source-" + ++this.sourceId
    return this.sourceMap.set(e, o), this.reverseSourceLookup.set(o, e), o
  }
}
const avtiveSensor = new Map()
const allSensor = new Map()
export default {
  createSensor: (sdk, dependencies) => {
    const ownSensor = new Map(),
      a = SdkCommand.create(new DependencyFetcher(avtiveSensor, allSensor), new ClassFactory(SensorAddSourceExecutor)),
      s = SdkCommand.create(new DependencyFetcher(avtiveSensor, ownSensor), new ClassFactory(DisposeSensorExecutor)),
      i = SdkCommand.create(DependencyFetcher.extend(dependencies, avtiveSensor), new ClassFactory(SensorShowDebugExecutor))
    sdk.addCommand("Sensor.addSource", a)
    sdk.addCommand("sensor.dispose", s)
    sdk.addCommand("sensor.showDebug", i)
    const d = { [sensorTypeEnum.CAMERA]: new CameraSensorFactory() }
    const c = SdkCommand.create(DependencyFetcher.extend(dependencies, avtiveSensor, ownSensor), new ClassFactory(CreateSensorExecutor, sdk, d))
    sdk.addCommandToInterface({ namespace: "Sensor", name: "createSensor", subRoutine: "sensor.create", args: [] }, c)
  },
  createSource: (sdk, THREE, exportOBJ) => {
    const a = new DependencyFetcher(allSensor),
      s = {
        [sourceTypeEnum.SPHERE]: new SphereSourceUpdater(),
        [sourceTypeEnum.BOX]: new BoxSourceUpdater(),
        [sourceTypeEnum.CYLINDER]: new CylinderSourceUpdater()
      },
      i = SdkCommand.create(a, new ClassFactory(UpdateSourceExecutor, s))
    sdk.addCommand("Sensor.updateSource", i)
    const d = {
      [sourceTypeEnum.SPHERE]: new SphereSourceFactory(sourceTypeEnum, exportOBJ.sensorVolumes.sphere, THREE),
      [sourceTypeEnum.BOX]: new BoxSourceFactory(sourceTypeEnum, exportOBJ.sensorVolumes.box, THREE),
      [sourceTypeEnum.CYLINDER]: new CylinderSourceFactory(sourceTypeEnum, exportOBJ.sensorVolumes.cylinder, THREE)
    }
    const c = SdkCommand.create(a, new ClassFactory(CreateSourceExecutor, d))
    sdk.addCommandToInterface({ namespace: "Sensor", name: "createSource", subRoutine: "source.create", args: ["type", "options"] }, c)
  },
  SensorType: sdk => {
    sdk.addEnumToInterface({
      namespace: "Sensor",
      name: "SensorType",
      values: sensorTypeEnum
    })
  },
  SourceType: sdk => {
    sdk.addEnumToInterface({
      namespace: "Sensor",
      name: "SourceType",
      values: sourceTypeEnum
    })
  }
}
